本系列文章會在筆者的部落格繼續連載!Design System 101 感謝大家的閱讀!
稍微繁忙的一天!決定先介紹兩個 Design System 常用的 Hooks useSafeLayoutEffect 以及 useComposedRefs!
useLayoutEffect 是 React 提供的一個 Hook,跟 useEffect 一樣,都是用來處理 Side Effect 的,不同的是 useLayoutEffect 其執行時間是瀏覽器繪製 DOM 之前,且在 SSR 的時候,會噴出錯誤:
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
主要是因為 useLayoutEffect 不會在 Server Side 執行,這時候就可以使用 useSafeLayoutEffect 來解決這個問題。
而 useSafeLayoutEffect 的實作,概念上很簡單就是將 useLayoutEffect 在 Server Side 的時候,改用 useEffect 來執行。
import {useLayoutEffect, useEffect} from 'react';
const useSafeLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
useComposedRefs 通常是用來合併多個 ref, 而這在 Design System 中是很常見的。
例如有時後我們的需要同時控制組件內部某個元素,並且也能夠讓外部開發者也能控制這個元素。這時候就可以使用 useComposedRefs 來解決這個問題。
舉例來說:
當這個組件 mounted (渲染完成) 的時候,自動 focus 到輸入框上。同時,如果也希望讓其他使用者可以拿到輸入框的值,這時候就可以使用 useComposedRefs 來解決這個問題。
import React, { forwardRef, useEffect, useRef } from 'react';
import { useComposedRefs } from './compose-refs';
const FocusableInput = forwardRef((props, externalRef) => {
  const internalRef = useRef();
  const composedRef = useComposedRefs(externalRef, internalRef);
  useEffect(() => {
    if (internalRef.current) {
      internalRef.current.focus();
    }
  }, []);
  return <input ref={composedRef} {...props} />;
});
export default () => {
  const inputRef = useRef(null);
  const handleButtonClick = () => {
    if (inputRef.current) {
      console.log('輸入框的值是', inputRef.current.value);
    }
  };
  return (
    <div>
      <FocusableInput ref={inputRef} placeholder="Placeholder" />
      <button onClick={handleButtonClick}>Submit</button>
    </div>
  );
};
import React, { useCallback } from 'react';
function assignRef(ref, value) {
  if (ref == null) {
    return;
  }
  if (typeof ref === 'function') {
    ref(value);
  } else {
    Reflect.set(ref, 'current', value);
  }
}
export function useComposedRefs(...refs) {
  return React.useCallback(
    (value) => {
      refs.forEach((ref) => {
        assignRef(ref, value);
      });
    },
    [refs],
  );
}
